import os
import sys
import subprocess
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
from datetime import datetime
import threading
import queue
from typing import Optional
import socket
import traceback

from constants import (
    APP_NAME,
    APP_AUTHOR,
    APP_VERSION,
    MIN_WINDOW_WIDTH,
    MIN_WINDOW_HEIGHT,
    DEFAULT_WINDOW_WIDTH,
    DEFAULT_WINDOW_HEIGHT,
    WUSA_PATH,
    DISM_PATH,
    SHUTDOWN_PATH,
    LOG_FORMAT,
    LOG_FILE_MAX_LINES,
    PROGRESS_UPDATE_INTERVAL,
    LOG_QUEUE_PROCESS_INTERVAL,
    SASH_POSITION_DELAY,
    SUPPORTED_FILE_TYPES,
    SUCCESS_EXIT_CODES,
    MAX_PROCESS_OUTPUT_LINES,
    DEFAULT_REBOOT_DELAY
)
from utils import (
    describe_dism_code,
    describe_wusa_code,
    describe_exe_code,
    format_exit_code,
    is_legacy_windows,
    check_patch_compatibility,
    extract_kb_id,
    get_local_ip,
    extract_wusa_reason,
    collect_failure_logs,
    read_log_text
)

class PatchInstallerGUI:
    def __init__(self, root):
        self.root = root
        self.root.title(f"{APP_NAME}@{APP_AUTHOR}")
        self.root.geometry(f"{DEFAULT_WINDOW_WIDTH}x{DEFAULT_WINDOW_HEIGHT}")
        self.root.resizable(True, True)
        # 改善窗口化显示：设置最小尺寸，避免内容被压缩
        try:
            self.root.minsize(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
        except Exception:
            pass
        # 初始化变量
        self.patch_files = []  # 补丁文件列表
        if getattr(sys, "frozen", False):
            self.current_dir = os.path.dirname(sys.executable)
        else:
            self.current_dir = os.path.dirname(os.path.abspath(__file__))
        self.log_file = os.path.join(self.current_dir, f"{datetime.now().strftime('%Y%m%d')}_PatchInstall.log")
        self.need_reboot = False
        self.is_installing = False
        self.log_queue = queue.Queue()
        self.current_proc = None
        self.progress_text = tk.StringVar(value="等待开始")
        self.progress_max = 0
        # 统计计数
        self.success_count = 0
        self.failure_count = 0
        self.reboot_needed_count = 0
        self.was_canceled = False
        self.failed_patch_ids = []
        # 状态栏变量
        self.status_var = tk.StringVar(value="就绪")
        # 低版本Windows进度相关
        self.legacy_progress_timer = None
        self.legacy_progress_percent = 0
        self.current_patch_index = -1
        self.is_legacy_windows_system = is_legacy_windows()

        # 创建界面
        self.create_widgets()
        # 启动日志队列处理，避免后台线程直接操作UI
        self.root.after(LOG_QUEUE_PROCESS_INTERVAL, self.process_log_queue)
        self.log(f"日志文件路径: {self.log_file}")
        self.load_patch_files()

    def create_widgets(self):
        # 标题
        header = ttk.Label(self.root, text=APP_NAME, font=("微软雅黑", 14, "bold"))
        header.pack(pady=10)
        # 使用说明
        usage = (
            "使用说明：本软件不会自动判断相关依赖；安装补丁前请按安装顺序对文件名编号，" 
            "例如：'001-KB0000.msu；002-KB0001.cab；003-KB0003.exe'，其中 001 为首个安装的补丁。建议所有补丁统一按编号排序后再开始安装。"
        )
        usage_label = ttk.Label(self.root, text=usage, foreground="gray", wraplength=MIN_WINDOW_WIDTH - 40, font=("微软雅黑", 10))
        usage_label.pack(pady=(0, 8))
        # 路径与操作区
        path_frame = ttk.Frame(self.root)
        path_frame.pack(fill=tk.X, padx=20)
        ttk.Label(path_frame, text="当前工作目录:").pack(side=tk.LEFT)
        self.path_label = ttk.Label(path_frame, text=self.current_dir, foreground="blue")
        self.path_label.pack(side=tk.LEFT, padx=6)

        self.install_btn = ttk.Button(path_frame, text="开始安装", command=self.start_installation, state=tk.DISABLED)
        self.install_btn.pack(side=tk.RIGHT, padx=6)
        ttk.Button(path_frame, text="选择目录", command=self.browse_directory).pack(side=tk.RIGHT, padx=6)

        # 底部操作区（先创建并固定在底部，预留空间避免被挤压）
        btn_frame = ttk.Frame(self.root)
        btn_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=20, pady=(6, 10))
        # 状态栏显示当前处理的补丁ID
        status_frame = ttk.Frame(btn_frame)
        status_frame.pack(fill=tk.X, pady=(2, 2))
        status_label = ttk.Label(status_frame, text="当前状态:", font=("微软雅黑", 9))
        status_label.pack(side=tk.LEFT)
        self.status_display = ttk.Label(status_frame, textvariable=self.status_var, foreground="darkblue", font=("微软雅黑", 9, "italic"))
        self.status_display.pack(side=tk.LEFT, padx=4)
        # 进度文案
        progress_frame = ttk.Frame(btn_frame)
        progress_frame.pack(fill=tk.X, pady=(2, 2))
        self.progress_label = ttk.Label(progress_frame, textvariable=self.progress_text, font=("微软雅黑", 10))
        self.progress_label.pack(side=tk.LEFT, padx=8)
        # 控制按钮
        self.cancel_btn = ttk.Button(progress_frame, text="取消", command=self.cancel_installation, state=tk.DISABLED)
        self.cancel_btn.pack(side=tk.LEFT, padx=8)
        self.reboot_btn = ttk.Button(progress_frame, text="重启系统", command=self.reboot_system, state=tk.DISABLED)
        self.reboot_btn.pack(side=tk.RIGHT, padx=8)

        # 中部采用可调整比例的垂直分割布局，使列表与日志在窗口化时保持协调
        paned = ttk.Panedwindow(self.root, orient=tk.VERTICAL)
        paned.pack(fill=tk.BOTH, expand=True, padx=20, pady=(0, 6))

        # 待安装补丁列表（上半部分）
        list_frame = ttk.LabelFrame(paned, text="待安装补丁列表")
        paned.add(list_frame, weight=3)
        self.patch_listbox = tk.Listbox(list_frame, font=("微软雅黑", 10), selectmode=tk.EXTENDED)
        self.patch_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=6, pady=(6, 2))
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.patch_listbox.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y, pady=6)
        self.patch_listbox.config(yscrollcommand=scrollbar.set)
        
        # 安装进度条
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(list_frame, variable=self.progress_var, length=100)
        self.progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=6, pady=(2, 6))

        # 安装日志（下半部分）
        log_frame = ttk.LabelFrame(paned, text="安装日志")
        paned.add(log_frame, weight=2)
        self.log_text = scrolledtext.ScrolledText(log_frame, font=("微软雅黑", 10), wrap=tk.WORD, state=tk.DISABLED)
        self.log_text.pack(fill=tk.BOTH, expand=True, padx=6, pady=6)

        # 初始分割比例（约 55% 列表，45% 日志），并允许用户拖动调整
        self.root.after(SASH_POSITION_DELAY, lambda: paned.sashpos(0, int(paned.winfo_height() * 0.55)))

    def _start_legacy_progress_timer(self):
        """为低版本Windows启动进度更新定时器"""
        if not self.is_installing or not self.is_legacy_windows_system:
            return
            
        # 估算补丁安装进度，基于文件类型和大小
        try:
            patch_path = self.patch_files[self.current_patch_index]
            patch_name = os.path.basename(patch_path)
            ext = os.path.splitext(patch_name)[1].lower()
            
            # 默认进度增长速度
            progress_increment = 0.5
            
            # 根据文件类型和大小调整进度增长速度
            try:
                file_size = os.path.getsize(patch_path)
                if file_size > 200 * 1024 * 1024:  # 大于200MB的大补丁
                    if ext == ".cab":
                        progress_increment = 0.3
                    elif ext == ".msu":
                        progress_increment = 0.5
                    else:  # .exe
                        progress_increment = 0.4
                elif file_size > 100 * 1024 * 1024:  # 大于100MB的中等补丁
                    if ext == ".cab":
                        progress_increment = 0.4
                    elif ext == ".msu":
                        progress_increment = 0.6
                    else:  # .exe
                        progress_increment = 0.5
                else:  # 小补丁
                    if ext == ".cab":
                        progress_increment = 0.6
                    elif ext == ".msu":
                        progress_increment = 0.8
                    else:  # .exe
                        progress_increment = 0.7
            except Exception:
                # 如果无法获取文件大小，仅根据文件类型设置
                if ext == ".msu":
                    progress_increment = 0.7  # MSU文件通常安装稍快
                elif ext == ".cab":
                    progress_increment = 0.4  # CAB文件通过DISM安装较慢
                elif ext == ".exe":
                    progress_increment = 0.6  # EXE安装器速度中等
            
            # 增加进度并更新显示
            self.legacy_progress_percent = min(99, self.legacy_progress_percent + progress_increment)
            self.progress_text.set(f"{self.current_patch_index + 1}/{self.progress_max} - {self.legacy_progress_percent:.0f}%")
            
            # 更频繁地更新进度，提升用户体验
            if self.is_installing:
                self.legacy_progress_timer = self.root.after(PROGRESS_UPDATE_INTERVAL, self._start_legacy_progress_timer)
        except Exception as e:
            self.log_async(f"更新进度定时器异常：{str(e)}", "error")
            # 发生异常时仍然尝试继续更新，避免进度卡住
            if self.is_installing:
                self.legacy_progress_timer = self.root.after(2000, self._start_legacy_progress_timer)
            
    def _stop_legacy_progress_timer(self):
        """停止进度更新定时器"""
        if self.legacy_progress_timer:
            try:
                self.root.after_cancel(self.legacy_progress_timer)
            except Exception:
                pass
            self.legacy_progress_timer = None
            
    def update_progress(self, current: int, total: int):
        try:
            # 计算总体进度百分比
            if total > 0:
                # 确保current不超过total
                current = min(current, total)
                
                # 基础进度（已安装的补丁数）
                base_progress = (current - 1) / total * 100 if current > 0 else 0
                
                # 如果是低版本Windows且正在处理补丁，包含当前补丁的估算进度
                if self.is_legacy_windows_system and current <= total and self.is_installing and hasattr(self, 'legacy_progress_percent'):
                    # 当前补丁的估算进度（占总进度的1/total比例）
                    current_patch_progress = self.legacy_progress_percent / total
                    total_progress = base_progress + current_patch_progress
                    # 确保进度不超过99%（留1%给最终完成时设置）
                    display_progress = min(99, total_progress)
                    self.progress_text.set(f"{current}/{total} - {display_progress:.0f}%")
                    self.progress_var.set(display_progress)
                else:
                    # 普通进度显示
                    self.progress_text.set(f"{current}/{total}")
                    self.progress_var.set(base_progress)
            else:
                self.progress_text.set(f"{min(current, total)}/{total}")
                self.progress_var.set(0)
        except Exception as e:
            # 添加更详细的异常日志记录
            self.log_async(f"更新进度时发生异常：{str(e)}", "error")

    def browse_directory(self):
        """浏览并选择补丁目录"""
        from tkinter import filedialog
        try:
            dir_path = filedialog.askdirectory(title="选择补丁目录", initialdir=self.current_dir)
            if dir_path and os.path.isdir(dir_path):
                self.current_dir = dir_path
                self.path_label.config(text=self.current_dir)
                self.load_patch_files()
        except Exception as e:
            self.log(f"浏览目录失败：{str(e)}", "error")
            messagebox.showerror("错误", f"浏览目录失败：{str(e)}")

    def load_patch_files(self):
        """加载目录中的补丁文件"""
        try:
            self.patch_files = []
            self.patch_listbox.delete(0, tk.END)
            
            # 跳过自身（仅在打包为EXE时）
            self_exe_path = os.path.abspath(sys.executable) if getattr(sys, "frozen", False) else None
            
            for file in sorted(os.listdir(self.current_dir)):
                if file.lower().endswith(SUPPORTED_FILE_TYPES):
                    full_path = os.path.join(self.current_dir, file)
                    if self_exe_path and os.path.abspath(full_path) == self_exe_path:
                        continue
                    
                    # 检查兼容性
                    is_compat, reason = check_patch_compatibility(file)
                    if not is_compat:
                        # 不兼容的补丁显示为红色
                        self.patch_listbox.insert(tk.END, f"❌ {file} ({reason})")
                        self.patch_listbox.itemconfig(tk.END, fg="red")
                    else:
                        # 兼容的补丁正常显示
                        self.patch_listbox.insert(tk.END, file)
                        self.patch_files.append(full_path)
            
            # 启用/禁用安装按钮
            self.install_btn.config(state=tk.NORMAL if self.patch_files else tk.DISABLED)
            self.log(f"已加载 {len(self.patch_files)} 个补丁文件")
        except Exception as e:
            self.log(f"加载补丁文件失败：{str(e)}", "error")

    def log(self, message, level="info"):
        """记录日志（主线程）"""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        level_color = {
            "info": "black",
            "success": "green",
            "warning": "orange",
            "error": "red"
        }
        color = level_color.get(level, "black")
        level_prefix = f"[{level.upper()}] " if level != "info" else ""
        
        log_line = f"[{timestamp}] {level_prefix}{message}"
        
        # 写入日志文件
        try:
            with open(self.log_file, "a", encoding="utf-8") as f:
                f.write(log_line + "\n")
        except Exception as e:
            # 如果无法写入日志文件，至少在界面上显示错误
            error_msg = f"写入日志文件失败: {str(e)}"
            self.log_text.config(state=tk.NORMAL)
            self.log_text.insert(tk.END, error_msg + "\n", "error")
            self.log_text.see(tk.END)
            self.log_text.config(state=tk.DISABLED)
        
        # 显示到日志文本框
        self.log_text.config(state=tk.NORMAL)
        self.log_text.insert(tk.END, log_line + "\n", level)
        
        # 限制日志行数，避免内存占用过高
        max_lines = LOG_FILE_MAX_LINES
        line_count = int(self.log_text.index('end-1c').split('.')[0])
        if line_count > max_lines:
            self.log_text.delete(1.0, f"{line_count - max_lines}.0")
        
        self.log_text.see(tk.END)
        self.log_text.config(state=tk.DISABLED)
        
        # 配置文本颜色标签
        self.log_text.tag_config("info", foreground="black")
        self.log_text.tag_config("success", foreground="green")
        self.log_text.tag_config("warning", foreground="orange")
        self.log_text.tag_config("error", foreground="red")
        
        # 更新状态栏
        self.status_var.set(f"{level_prefix}{message}")

    def log_async(self, message, level="info"):
        """异步记录日志（从工作线程调用）"""
        self.log_queue.put((message, level))

    def process_log_queue(self):
        """处理日志队列"""
        while not self.log_queue.empty():
            message, level = self.log_queue.get()
            self.log(message, level)
        self.root.after(LOG_QUEUE_PROCESS_INTERVAL, self.process_log_queue)

    def start_installation(self):
        """开始安装补丁"""
        try:
            if not self.patch_files:
                messagebox.showinfo("提示", "没有可安装的补丁文件")
                return
            
            # 重置状态
            self.is_installing = True
            self.was_canceled = False
            self.need_reboot = False
            self.success_count = 0
            self.failure_count = 0
            self.reboot_needed_count = 0
            self.failed_patch_ids = []
            self.progress_max = len(self.patch_files)
            self.progress_var.set(0)
            
            # 更新UI状态
            self.install_btn.config(state=tk.DISABLED)
            self.cancel_btn.config(state=tk.NORMAL)
            self.reboot_btn.config(state=tk.DISABLED)
            
            # 开始安装线程
            install_thread = threading.Thread(target=self.install_patches_worker, daemon=True)
            install_thread.start()
            
            self.log(f"开始安装 {self.progress_max} 个补丁")
        except Exception as e:
            self.log(f"启动安装失败：{str(e)}", "error")
            messagebox.showerror("错误", f"启动安装失败：{str(e)}")

    def install_patches_worker(self):
        """补丁安装工作线程"""
        try:
            for i, patch_path in enumerate(self.patch_files):
                if self.was_canceled:
                    self.log_async("安装已取消", "warning")
                    break
                
                self.current_patch_index = i
                patch_name = os.path.basename(patch_path)
                patch_id = extract_kb_id(patch_name)
                
                # 更新状态栏显示当前处理的补丁ID
                self.root.after(0, lambda pid=patch_id: self.status_var.set(f"正在处理: {pid}"))
                
                self.log_async(f"[{i + 1}/{self.progress_max}] 开始安装：{patch_name}")
                self.update_progress(i + 1, self.progress_max)
                
                # 低版本Windows启动进度定时器
                if self.is_legacy_windows_system:
                    self.legacy_progress_percent = 0
                    self._stop_legacy_progress_timer()
                    self.root.after(0, self._start_legacy_progress_timer)
                
                # 创建补丁日志文件
                patch_log = os.path.join(self.current_dir, f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{os.path.splitext(patch_name)[0]}.log")
                ext = os.path.splitext(patch_name)[1].lower()
                cmd = None
                
                try:
                    # 构建安装命令
                    if ext == ".msu":
                        if not os.path.isfile(WUSA_PATH):
                            self.failure_count += 1
                            self.failed_patch_ids.append(patch_id)
                            self.log_async(f"未找到 wusa.exe：{WUSA_PATH}", "error")
                            continue
                        cmd = [WUSA_PATH, patch_path, "/quiet", "/norestart", f"/log:{patch_log}"]
                        self.log_async(f"[{i + 1}/{self.progress_max}] 使用 wusa.exe 安装 MSU 补丁")
                    elif ext == ".cab":
                        if not os.path.isfile(DISM_PATH):
                            self.failure_count += 1
                            self.failed_patch_ids.append(patch_id)
                            self.log_async(f"未找到 dism.exe：{DISM_PATH}", "error")
                            continue
                        abs_patch = os.path.abspath(patch_path)
                        cmd = [
                            DISM_PATH,
                            "/online",
                            "/add-package",
                            f"/packagepath:{abs_patch}",
                            "/norestart",
                            f"/logpath:{patch_log}",
                        ]
                        self.log_async(f"[{i + 1}/{self.progress_max}] 使用 dism.exe 安装 CAB 补丁")
                    elif ext == ".exe":
                        cmd = [patch_path, "/quiet", "/norestart"]
                        self.log_async(f"[{i + 1}/{self.progress_max}] 执行 EXE 安装程序")
                    else:
                        self.failure_count += 1
                        self.failed_patch_ids.append(patch_id)
                        self.log_async(f"不支持的文件类型：{ext}", "error")
                        continue
                    
                    # 记录命令
                    self.log_async(f"[{i + 1}/{self.progress_max}] 执行命令: {' '.join(cmd)}")
                    
                    # 执行安装命令
                    self.current_proc = subprocess.Popen(
                        cmd,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        text=True,
                        creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0),
                        shell=False  # 安全考虑，避免shell=True
                    )
                    
                    # 实时读取输出
                    line_buffer = {"stdout": "", "stderr": ""}
                    max_output_lines = MAX_PROCESS_OUTPUT_LINES  # 每个流最多显示的行数
                    line_count = {"stdout": 0, "stderr": 0}
                    
                    while self.current_proc.poll() is None and not self.was_canceled:
                        # 读取stdout
                        if self.current_proc.stdout and line_count["stdout"] < max_output_lines:
                            line = self.current_proc.stdout.readline()
                            if line:
                                line = line.strip()
                                if line:
                                    self.log_async(f"[{i + 1}/{self.progress_max}] {line}")
                                    line_count["stdout"] += 1
                                    if line_count["stdout"] == max_output_lines:
                                        self.log_async(f"[{i + 1}/{self.progress_max}] ... 更多标准输出已省略")
                        
                        # 读取stderr
                        if self.current_proc.stderr and line_count["stderr"] < max_output_lines:
                            line = self.current_proc.stderr.readline()
                            if line:
                                line = line.strip()
                                if line:
                                    self.log_async(f"[{i + 1}/{self.progress_max}] {line}", "error")
                                    line_count["stderr"] += 1
                                    if line_count["stderr"] == max_output_lines:
                                        self.log_async(f"[{i + 1}/{self.progress_max}] ... 更多错误输出已省略", "error")
                        
                        # 小延迟避免CPU占用过高
                        import time
                        time.sleep(0.1)
                    
                    # 检查是否取消
                    if self.was_canceled:
                        try:
                            if self.current_proc.poll() is None:
                                self.current_proc.terminate()
                                self.current_proc.wait(timeout=5)
                        except Exception:
                            pass
                        break
                    
                    # 获取返回码
                    rc = self.current_proc.returncode
                    
                    # 处理返回结果
                    if rc == 0:
                        self.success_count += 1
                        self.log_async(f"安装成功：{patch_name}（无需重启）", "success")
                    elif rc in SUCCESS_EXIT_CODES and rc != 0:
                        self.success_count += 1
                        self.reboot_needed_count += 1
                        self.need_reboot = True
                        self.log_async(f"安装成功：{patch_name}（需重启生效）", "success")
                    else:
                        self.failure_count += 1
                        self.failed_patch_ids.append(patch_id)
                        
                        # 根据文件类型选择错误描述
                        if ext == ".exe":
                            desc = describe_exe_code(rc)
                        elif ext == ".cab":
                            desc = describe_dism_code(rc)
                        else:
                            desc = describe_wusa_code(rc)
                            # 尝试从WUSA日志中提取更详细的失败原因
                            wusa_reason = extract_wusa_reason(patch_log)
                            if wusa_reason:
                                desc += f" - {wusa_reason}"
                        
                        self.log_async(f"安装失败：{patch_name}，返回码：{format_exit_code(rc)}，{desc}", "error")
                        
                        # 收集失败日志
                        log_name = collect_failure_logs(self.current_dir, patch_name, patch_log, " ".join(cmd), rc)
                        if log_name:
                            self.log_async(f"已归档失败日志：{log_name}")
                    
                    # 更新进度到100%（当前补丁）
                    if self.is_legacy_windows_system:
                        self.legacy_progress_percent = 100
                        self.update_progress(i + 1, self.progress_max)
                
                except Exception as e:
                    self.failure_count += 1
                    self.failed_patch_ids.append(patch_id)
                    self.log_async(f"安装异常：{patch_name}，{str(e)}", "error")
                    
                    # 收集异常日志
                    try:
                        cmd_str = " ".join(cmd) if 'cmd' in locals() else ""
                        error_details = traceback.format_exc()
                        log_name = collect_failure_logs(self.current_dir, patch_name, patch_log, cmd_str, error_details=error_details)
                        if log_name:
                            self.log_async(f"已归档异常日志：{log_name}")
                    except Exception as inner_e:
                        self.log_async(f"收集错误日志失败：{str(inner_e)}", "error")
                
                finally:
                    # 停止进度定时器
                    self._stop_legacy_progress_timer()
                    self.current_proc = None
            
        finally:
            # 更新状态栏
            self.root.after(0, lambda: self.status_var.set("就绪"))
            
            # 完成安装
            self.root.after(0, self.on_installation_complete)

    def on_installation_complete(self):
        """安装完成处理"""
        try:
            self.is_installing = False
            self.progress_var.set(100)
            self.progress_text.set("安装完成")
            
            # 更新UI状态
            self.cancel_btn.config(state=tk.DISABLED)
            if self.need_reboot:
                self.reboot_btn.config(state=tk.NORMAL)
            
            # 写入失败补丁列表
            if self.failed_patch_ids:
                self.write_failed_patches_txt(os.path.join(self.current_dir, "安装失败补丁.txt"))
            
            # 显示总结
            summary = f"安装完成：成功 {self.success_count}，失败 {self.failure_count}，需重启 {self.reboot_needed_count}"
            self.log(summary, "success")
            
            if self.was_canceled:
                messagebox.showinfo("提示", "安装已取消")
            else:
                messagebox.showinfo("完成", summary)
                
            # 如果需要重启，给出提示
            if self.need_reboot:
                self.log("部分补丁需要重启才能生效", "warning")
        except Exception as e:
            self.log(f"处理安装完成事件时出错：{str(e)}", "error")

    def write_failed_patches_txt(self, path: str):
        """写入失败补丁列表"""
        try:
            with open(path, "w", encoding="utf-8") as f:
                f.write(f"失败补丁列表（{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}）\n")
                f.write(f"主机名：{socket.gethostname()}\n")
                f.write(f"IP地址：{get_local_ip()}\n\n")
                for i, patch_id in enumerate(self.failed_patch_ids, 1):
                    f.write(f"{i}. {patch_id}\n")
        except Exception:
            pass

    def cancel_installation(self):
        """取消安装"""
        try:
            if messagebox.askyesno("确认", "确定要取消安装吗？"):
                self.was_canceled = True
                self.log("正在取消安装...", "warning")
                
                # 尝试终止当前进程
                if self.current_proc:
                    try:
                        self.current_proc.terminate()
                    except Exception:
                        pass
        except Exception as e:
            self.log(f"取消安装时出错：{str(e)}", "error")

    def reboot_system(self):
        """重启系统"""
        try:
            if messagebox.askyesno("确认重启", "重启系统后，部分补丁将生效。确定要立即重启吗？"):
                self.log("正在重启系统...", "warning")
                # 优先使用常量中定义的shutdown路径
                shutdown_cmd = SHUTDOWN_PATH if os.path.isfile(SHUTDOWN_PATH) else "shutdown"
                # 使用shutdown命令重启，延迟指定秒数
                subprocess.run([shutdown_cmd, "/r", "/t", str(DEFAULT_REBOOT_DELAY), "/f"], shell=False)
                messagebox.showinfo("提示", f"系统将在{DEFAULT_REBOOT_DELAY}秒后重启")
        except Exception as e:
            self.log(f"重启系统失败：{str(e)}", "error")
            messagebox.showerror("错误", f"重启系统失败：{str(e)}")